<?php
/**
 * Ce fichier définit les classes suivantes :
 * FrontController : Récupère les requêtes http et les traites
 * Controller : Classe abstraite qui définit le comportement des contrôleurs
 * View : Classe qui définit le comportement des vues
 * SQLAdapt : Classe qui permet de se connecter à la base de données
 * Modele : Permet d'effectuer facilement des requêtes sur des occurences
 * Collection : Permet d'effectuer facilement des requêtes sur une table
**/


/**
 * La classe SQLAdapt permet d'attaquer une base de données grâce à la technologie PDO.
**/
class SQLAdapt
{
	// Le pilote à utiliser ou alors le profil
	// private $_pilot;
	
	/**
	 * Constructeur
	**/
	public function SQLAdapt()
	{
		//$this->_pilot = $pilot;
	}
	
	
	/**
	 * Initie une connexion en fonction de l'adaptateur sélectionné.
	**/
	private function connect()
	{
		$conn = new PDO('mysql:host=localhost;dbname=blog','adminZii','$Mt1.87$');
		//$conn = new PDO('mysql:host=db1855.1and1.fr;dbname=db308398853', 'dbo308398853', '$lXs07!Loud');
		
		$conn->query("set names 'utf8'");
		
		return $conn;
	}
	
	
	/**
	 * Permet d'exécuter une requête de type update, insert...
	 * Param : la requête à exécuter
	 * Param (optionnel) : si true, alors la fonction renvoit le dernier id inséré
	**/
	 public function exec($req, $returnId = false)
	 {
		$conn = $this->connect();
		$res = $conn->exec($req);
		if($returnId)
			return $conn->lastInsertId();
		else
			return $res;
	 }

	 /**
	  * Permet d'exécuter une requête de type select...
	 **/
	 public function query($req)
	 {
	 	$conn = $this->connect();
		$res = $conn->query($req);
		return $res;
	 }
}


/**
 * La classe Modele permet de représenter les diverses occurences de la base
 * de données sous forme d'objet. 
**/
abstract class Modele
{
	// Nom de la table
	private $_table;
	// Nom de la clé primaire (peut être un tableau)
	private $_primary_key;
	
	
	/**
	 * Constructeur 
	**/
	public function Modele($table, $pk)
	{
		$this->_table = $table;
		$this->_primary_key = $pk;
	}
	
	
	/**
	 * Enregistre (insert) l'objet en tant qu'occurence dans la base de données. Si elle existe déjà,
	 * l'enregistrement se résume alors à une mise à jour (update)
	**/
	public function save()
	{
		// Si l'objet possède une valeur pour sa clé primaire
		// cela signifie que l'objet existe en BDD
		if(is_array($this->_primary_key))
		{
			$id = array();
			$i=0;
			foreach($this->_primary_key as $key)
			{
				$method = "get_".$key;
				$id[$i] = call_user_func(array($this,$method));
				$i++;
			}
			//print_r($id);
		}
		else
		{
			$method = "get_".$this->_primary_key;
			$id = call_user_func(array($this,$method));
		}
		
		$constructeur = $this->_table."_Collection";
		$collection = new $constructeur;
		//die("ok");
		if($id == null || $collection->getById($id) == null)
			return $this->insert();
		else
			$this->update();
			
	}
	
	
	
	/**
	 * Permet d'insérer une occurence dans la BDD
	**/
	private function insert()
	{
		// echo "insert<br/>";
		$adapt = new SQLAdapt();
		
		$req = "insert into $this->_table ";
		$req .= "values(";
		$cols = $this->getField($adapt);
		$firstField = true;
		foreach($cols as $c)
		{
			$access = "get_$c[0]";
			if($firstField)
			{
				$val = call_user_func(array($this,$access));
				if(!is_null($val))
					$val = "'".$val."'";
				else
					$val = "null";
				$firstField = false;
			}
			else
			{
				$val = call_user_func(array($this,$access));
				if(!is_null($val))
					$val = ", '".$val."'";
				else
					$val = ", null";
			}
			$req .= $val;
		}
		$req .= ")";
		
		//die($req);
		// Exécution de la requête
		return $adapt->exec($req,true);
		
		// Récupération de l'id
		/*$req = "select last_insert_id() as id";
		$res = $adapt->query($req);
		$res = $res->fetch();
		print_r($res);
		return $res[0];*/
	}
	
	
	/**
	 * Permet de mettre à jour une occurence dans la BDD
	**/
	private function update()
	{
		// echo "update<br/>";
		$adapt = new SQLAdapt();
		
		$req = "update $this->_table ";
		$req .= "set ";
		
		$cols = $this->getField($adapt);
		$cols_name = array();
		$i = 0;
		foreach($cols as $c)
			$cols_name[$i++] = $c[0];
		$nbCols = count($cols_name);
		
		for($i=0;$i<$nbCols;$i++)
		{
			// Récupération de la valeur
			$access = "get_".$cols_name[$i];
			$val = call_user_func(array($this,$access));
			if(!is_null($val))
				$val = "'".$val."'";
			else
				$val = "null";
			// Construction de la requête
			if($i == 0)
			{
				$fin_req = "where ".$cols_name[$i]." = ".$val;
			}
			else
			{
				if($i == 1)
					$req .= $cols_name[$i]." = ".$val;
				else
					$req .= ", ".$cols_name[$i]." = ".$val;
			}
		}
		$req .= $fin_req;
		
		// Exécution de la requête
		$adapt->exec($req);
	}
	
	
	/**
	 * Permet de supprimer l'occurence de la base de données
	**/
	public function delete()
	{
		$method = "get_".$this->_primary_key;
		$id = call_user_func(array($this,$method));
		$req = "delete from $this->_table where $this->_primary_key = $id";
		
		$adapt = new SQLAdapt();
		$adapt->exec($req);
	}
	
	
	/**
	 * Retourne une tableau représentant le nom des champs de la table
	**/
	private function getField($adapt)
	{
		$cols = $adapt->query("show columns from $this->_table ");
		return $cols;
	}
}


/**
 * La classe Collection permet de gérer l'ensemble des occurences d'une table.
**/
abstract class Collection
{
	// Nom de la table
	private $_table;
	// Clé primaire
	private $_primary_key;
	// Nombre de champs de la table
	protected $_nbField;
	
	
	/**
	 * Constructeur 
	**/
	public function Collection($table, $pk)
	{
		$this->_table = $table;
		$this->_primary_key = $pk;
		// On récupère le nom de champs de la table
		$adapt = new SQLAdapt();
		$res = $adapt->query("SHOW COLUMNS FROM $table");
		$i=0;
		if($res)
		{
			foreach($res as $col)
				$i++;
			$this->_nbField = $i;
		}
	}
	
	
	/**
	 * Retourne la collection répondant aux clauses passées en paramètre
	**/
	public function liste($clause = null, $order = null, $limit = null, $first = 0)
	{
		$adapt = new SQLAdapt();
		$req = "select * from $this->_table ";
		
		// Clause
		if($clause)
		{
			$champs = array_keys($clause);
			$cond = '';
			for($i=0;$i<count($champs);$i++)
			{
				$cond .= $champs[$i].'=\''.$clause[$champs[$i]].'\'';
				if($i < count($champs) - 1)
					$cond .= ' and ';
			}
			$req .= ' where '.$cond;
		}
		
		// Order
		if($order)
		{
			$champs = array_keys($order);
			$orderBy = '';
			for($i=0;$i<count($champs);$i++)
			{
				if ($i == 0)
					$orderBy .= $champs[$i].' '.$order[$champs[$i]];
				else
					$orderBy .= ', '.$champs[$i].' '.$order[$champs[$i]];
			}
			$req .= ' order by '.$orderBy;
		}
		
		// Limitation du nombre de résultat
		if($limit != null && is_numeric($limit))
		{
			$req .= " limit $first,$limit";
		}
		
		// Exécution de la requête
		$res = $adapt->query($req);
		
		if($res != null)
		{
			// On choisi le constructeur
			$constructeur = ucfirst($this->_table)."_Modele::getRef";
			
			// On construit un tableau d'objet
			$tabObject = array();
			$i = 0;
			foreach($res as $obj)
			{
				// On construit le tableau de paramètre
				$param = array();
				for($j=0;$j<$this->_nbField;$j++)
					$param[$j] = $obj[$j];

				// On construit l'objet
				$occurence = call_user_func_array($constructeur, $param);
				$tabObject[$i] = $occurence;
				$i++;
			}
			return $tabObject;
		}
		else
			return null;
	}
	
	
	/**
	 * Retourne l'occurence correspondante à la clé primaire sous forme d'instance de la
	 * classe mdèle correspondante
	**/
	public function getById($pk)
	{
		$adapt = new SQLAdapt();
		if(is_array($this->_primary_key))
		{
			$req = "select * from $this->_table where ";
			for($i=0;$i<count($this->_primary_key);$i++)
			{
				if($i == 0)
					$req .= $this->_primary_key[$i]."=";
				else
					$req .= "and ".$this->_primary_key[$i]."=";
			}
		}
		else
			$req = "select * from $this->_table where $this->_primary_key = $pk";
		
		$res = $adapt->query($req);
		$res = $res->fetch();
						
		if($res != null)
		{
			// On choisi le constructeur
			$constructeur = ucfirst($this->_table)."_Modele::getRef";
			// On construit la liste des paramètres
			for($i=0;$i<$this->_nbField;$i++)
				$param[$i] = $res[$i];
			// On construit l'occurence
			$occurence = call_user_func_array($constructeur, $param);
			return $occurence;
		}
		else
			return null;
	}
	
	/**
	 * Retourne la collection répondant aux clauses passées en paramètre
	**/
	public function like($champ, $value, $operator)
	{
		$adapt = new SQLAdapt();
		$req = "select * from $this->_table where ";
		
		// Clause
		if(!is_array($value))
		{
			$req .= "$champ like '%$value%'";
		}
		else
		{
			for($i = 0; $i < count($value); $i++)
			{
				if($i == 0)
					$req .= "$champ like '%".$value[0]."%' ";
				else
					$req .= "$operator $champ like '%".$value[$i]."%' ";
			}
		}
		
		// Exécution de la requête
		$res = $adapt->query($req);
		
		if($res != null)
		{
			// On choisi le constructeur
			$constructeur = ucfirst($this->_table)."_Modele::getRef";
			
			// On construit un tableau d'objet
			$tabObject = array();
			$i = 0;
			foreach($res as $obj)
			{
				// On construit le tableau de paramètre
				$param = array();
				for($j=0;$j<$this->_nbField;$j++)
					$param[$j] = $obj[$j];

				// On construit l'objet
				$occurence = call_user_func_array($constructeur, $param);
				$tabObject[$i] = $occurence;
				$i++;
			}
			return $tabObject;
		}
		else
			return null;
	}
	
}


/**
 * La classe View permet d'associer des attributs aux vues.
 * Cela apporte l'avantages principal que la vue ne manipule pas directement
 * les données. (C'est le contrôleur qui les lui envoie)
**/
class View
{
	// Le contenu de la vue
	private $_contains;
	// Le nom du contrôleur
	private $_ctrl;
	// Le nom de la vue
	private $_act;

	/**
	 * Constructeur pour les vues partielles
	 * Contrôleur (optionnel) :
	 * Action (optionnel) :
	**/
	public function View($ctrl = "", $act = "")
	{
		// Gestion des contrôleurs et des actions par défaut (init dans les deux cas)
		if($ctrl != "")
		{
			$this->_ctrl = $ctrl;
			if($act == "")
				$act = 'init';
			$this->_act = $act;
			$this->setContains("views/".$ctrl."/".$act.".php");
		}

	}


	/**
	 * Permet de remplir la vue avec un fichier php
	**/
	public function setContains($file)
	{
		// Ouverture et lecture du fichier
		$fd = fopen($file,'r');
		if($fd)
		{
			$contents = fread($fd, filesize($file));
			// Le contenu de la vue est mit à jour
			$this->_contains .= $contents;
			fclose($fd);
		}
		else
			die('ERROR : le fichier `'.$file.'` n\' a pas pu &ecirc;tre ouvert');
	}
	
	
	/**
	 * Rend la vue en exécutant le code de son contenu.
	 * Les attributs de l'instance sont donc pris en compte.
	**/
	public function render()
	{
		eval($this->_contains);
	}

	/**
	 * Permet de renseigner le nom du contrôleur. Celui-ci est renseigné dans le constructeur
	 * sauf pour la vue header.
	**/
	public function setControllerName($name)
	{
		$this->_ctrl = $name;
	}
}


/**
 * La classe Header est une classe spécialisée de la classe View. Elle est automatiquement ajoutée à toutes les vues n'ayant
 * pas fait appel aux méthodes View::disableLayout ou View::disableView
**/
class Header extends View
{
	/**
	 * Constructeur
	**/
	public function Header()
	{
		$this->_keywords = "";
		$this->_description = "";
		$this->_title = "";
		$this->_meta = "";
		// Par défaut, la page est indexée
		$this->_noIndex = false;
		// Gestion des fichiers
		$this->_cssFiles = array();
		$this->_jsFiles = array();
		
	}

}

/**
 * le controlleur primaire récupère les requêtes http et les traite.
**/
class FrontController
{
	// réfèrence vers l'instance
	static private $_self;
	// Le contrôleur demandé
	private $_ctrl;
	// L'action demandée
	private $_action;


	/**
	 * Permet de contrôler l'instanciation de la classe (modèle Singleton)
	**/
	static public function init()
	{
		if (!isset(self::$_self))
		{
			self::$_self = new FrontController();
			return self::$_self;
		}
		else
			die('ERROR : La classe FrontController ne peut pas &ecirc;tre instanci&eacute;e deux fois.');
	}


	/**
	 * Le constructeur (privé pour le respect du modèle Singleton)
	 * Il récupère le contrôleur et l'action et vérifie leurs existances.
	**/
	private function FrontController()
	{
		// Par défaut, init est le contrôleur par défaut.
		// init est également l'action par défaut pour tous les contrôleurs
		$controller = (isset($_GET['ctrl']) ? $_GET['ctrl'] : 'init');
		if( isset($_GET['act']) && $_GET['act'] != '' )
			$action = $_GET['act'];
		else
			$action = "init";

		// On vérifie l'existence des fichiers requis
		$this->checkUrl($controller, $action);
		$this->_action = $action;
	}


	/**
	 * Vérifie l'existence du fichier controleur, de la bonne instanciation du controleur
	 * et de l'existence de l'action en tant que méthode de ce dernier
	**/
	private function checkUrl($controller, $action)
	{
		if(!file_exists('controllers/'.$controller.'.php'))
			//die('ERROR : Le controleur `'.$controller.'` n\'a pas &eacute;t&eacute; trouv&eacute;.');
			header("location:/error/errno=70&c=$controller");
		else
		{
			// Instanciation du contrôleur (le include_once renvoit en effet, une instance du contrôleur demandé)
			$this->_ctrl = include_once('controllers/'.$controller.'.php');
			if(!is_a($this->_ctrl,'Controller'))
				die('ERROR : Le fichier `controllers/'.$controller.'.php` ne retourne pas une instance d\'une classe de type `Controller`.');
			if(!$this->_ctrl->isAction($action))
				//die('ERROR : L\'action `'.$action.'` n\'a pas &eacute;t&eacute; trouv&eacute;e pour le contr&ocirc;leur `'.$controller.'`.');
				header("location:/error/errno=71&c=$controller&a=$action");
		}
	}


	/**
	 * Génère et affiche la vue
	**/
	public function dispatch()
	{
		$this->_ctrl->render($this->_action);
	}


	/**
	 * Permet de forcer le contrôleur frontal à éxécuter une action
	 * donnée d'un contrôleur donné.
	 * Attention, aucun contrôle n'est effectué sur la validité
	 * des valeurs passées en paramètres.
	**/
	public function forceDispatch($ctrl, $act)
	{
		include_once('controllers/'.$ctrl.'.php');
		$constructeur = $ctrl.'_controller';
		$this->_ctrl = new $constructeur;
		$this->_action = $act;

	}


	/**
	 * Permet d'ajouter un profil de connexion
	**/
	 public function addConnection($name, $host, $port, $adapt, $bdd, $user, $mdp)
	 {
	 	$conn = array();
	 	$conn['host'] = $host;
	 	$conn['port'] = $port;
	 	$conn['adapt'] = $adapt;
	 	$conn['dbname'] = $bdd;
	 	$conn['user'] = $user;
	 	$conn['mdp'] = $mdp;

		// Ajout du tableau à la collection de profils du contrôleur
		$this->_ctrl->addConnection($name, $conn);
	 }
	 
	 
	/**
	 * Renvoie la chaîne suivante : ctrl|act
	**/
	public function getGetters()
	{
		return $this->_ctrl->getName().'|'.$this->_action;
	}

	
	/**
	 * Permet de ne plus à avoir à inclure les fichiers des classes Modele et Collection
	 * pour en instancier un objet.
	**/
	public function modelsAutoload()
	{
		function __autoload($class)
		{
			// Recherche dans model
			if(file_exists('./models/'.ucfirst($class) . '.php'))
				require_once './models/'.ucfirst($class) . '.php';
			else if(file_exists('./common/'.ucfirst($class) . '.php'))
				require_once './common/'.ucfirst($class) . '.php';
			else
				throw(new Exception("Class '".$class."' cannot be found."));
			
		}
	}
};


/**
 * La classe abstraite Controller définit des méthodes utile
 * pour les classes filles.
**/
abstract class Controller
{
	// Booléen à true par défaut qui permet de désactiver le rendu des vues
	protected $_viewActivated;
	// Booléen à true par défaut qui permet de désactiver l'inclusion du layout dans la vue
	protected $_layoutActivated;
	// La vue associée
	protected $_view;
	// Nom du controleur
	protected $_name;
	// La vue représentant le header
	protected $_header;
	// Collection des profils de connexion
	protected $_connect;


	/**
	 * Constructeur
	**/
	public function Controller()
	{
		// Par défaut, la vue et le layout sont activés
		$this->_viewActivated = true;
		$this->_layoutActivated = true;

		// Récupération du nom du contrôleur
		$classe = get_class($this);
		$classe = explode('_',$classe);
		$this->_name = $classe[0];

		// On instancie la vue
		$this->_view = new View();

		// On instancie le header
		$this->_header = new Header();
		$this->_header->setControllerName($this->_name);
		
		// On charge le fichier header.html
		$this->_header->setContains('./inc/header.php');
	}


	/**
	 * Vérifie que l'action passée en paramètre est bien une méthode du contrôleur
	**/
	public function isAction($action)
	{
		return is_callable(array($this, $action ));
	}


	/**
	 * Désactive le rendu de la vue
	**/
	public function disableView()
	{
		$this->_viewActivated = false;
	}


	/**
	 * Désactive uniquement l'inclusion du layout (header.html et foot.html) dans la vue
	**/
	public function disableLayout()
	{
		$this->_layoutActivated = false;
	}


	/**
	 * Ajoute un fichier JS
	 * L'attribut _header, qui est une instance de View, possède une collection de fichier JS
	**/
	public function addJSFile($fichier)
	{
		array_push($this->_header->_jsFiles, $fichier);
	}


	/**
	 * Ajoute un fichier CSS
	 * L'attribut _header, qui est une instance de View, possède une collection de fichier CSS
	**/
	public function addCSSFile($fichier)
	{
		array_push($this->_header->_cssFiles, $fichier);
	}
	
	/**
	 * Définit le contenu de la balise méta description
	**/
	public function setDescription($desc)
	{
		$this->_header->_description = $desc;
	}
	
	/**
	 * Définit le contenu de la balise méta mot clé
	**/
	public function setKeyWords($keywords)
	{
		$this->_header->_keywords = $keywords;
	}
	
	/**
	 * Permet de reféfinir le titre de la page
	**/
	public function setTitle($title)
	{
		$this->_header->_title = $title;
	}
	
	/**
	 * Permet d'empêcher le référencement de la page
	**/
	public function setNoIndex($b)
	{
		$this->_header->_noIndex = $b;
	}
	
	/**
	 * Permet d'ajouter un ensemble de balises méta dans le header.
	 * Attention, aucun contrôle n'est effectué sur la validité des informations passées en paramètre !
	**/
	public function setAdditionalMetaMarkup($str)
	{
		$this->_header->_meta = $str;
	}


	/**
	 * Rend l'affichage
	**/
	public function render($action)
	{
		// On exécute l'action du contrôleur.
		$res = call_user_func_array(array($this,$action),array());
		if($this->_viewActivated)
		{
			if($this->_layoutActivated)
				$this->_header->render();
			// On récupère le chemin de la vue
			$cheminVue = 'views/'.$this->_name.'/'.$action.'.php';
			if(!file_exists($cheminVue))
				die('ERROR : La vue `'.$cheminVue.'` est inexistante.');
			$this->_view->setContains($cheminVue);
			$this->_view->render();
			if($this->_layoutActivated)
				include('footer.html');
		}
		else
		{
			// On affiche l'éventuel résultat, même si le rendu de la vue est
			// désactivée. Cela sert dans le cas de requête Ajax, qui nécessitent
			// l'éxécution d'un script PHP (qui correspond à une action)
			echo $res;
		}
	}


	/**
	 * Ajoute une vue partielle à une vue globale
	 * La vue est instanciée et son contenu est chargé
	 * Cette méthode renvoit l'instance au contrôleur appelant, afin
	 * que ce dernier puisse le lier à sa propre vue.
	**/
	public function addPartialView($ctrl, $act)
	{
		// Instanciation du contrôleur
		$nom_controleur = $ctrl.'_controller';
		if ($nom_controleur == get_class($this))
			$controleur = new $nom_controleur ;
		else
			$controleur = include_once('./controllers/'.$ctrl.'.php');

		// Exécution de l'action
		call_user_func_array(array($controleur,$act),array());

		// On récupère le contenu de la vue (sans header et footer)
		$cheminVue = 'views/'.$controleur->_name.'/'.$act.'.php';
		if(!file_exists($cheminVue))
			die('ERROR : La vue `'.$cheminVue.'` est inexistante.');
		$controleur->_view->setContains($cheminVue);

		// On retourne la vue
		return $controleur->_view;
	}


	/**
	 * Permet d'ajouter le profil de connexion donné par le contrôleur frontal
	 */
	 public function addConnection($name, $conn)
	 {
	 	$this->_connect[$name] = $conn;
	 }


	/**
	 * Retourne le tableau contenant les informations du profil de connexion demandé
	 */
	 public function getConnection($name)
	 {
	 	return $this->_connect[$name];
	 }
	 
	 
	/**
	 * Retourne le nom du contrôleur
	 */
	public function getName()
	{
		return $this->_name;
	}

};


?>
